package ms.unicorn.dsl.analysis.impl;

import ms.unicorn.Diagram;
import ms.unicorn.Element;
import ms.unicorn.dsl.ILanguageElement;
import ms.unicorn.dsl.LanguageRegistry;
import ms.unicorn.dsl.analysis.IConversionCache;
import ms.unicorn.dsl.analysis.IConversionScheme;
import ms.unicorn.dsl.runtime.ExecutionContext;
import ms.unicorn.dsl.runtime.SphynxInterpreter;
import ms.unicorn.dsl.runtime.VariableStorage;
import ms.unicorn.dsl.runtime.impl.DefaultExecutionContext;
import ms.unicorn.dsl.runtime.interpreter.XFunctionDeclartationInterpreter;
import ms.unicorn.dsl.unicornDSL.NamedProperty;
import ms.unicorn.dsl.unicornDSL.XFunctionDeclaration;
import ms.unicorn.dsl.unicornDSL.XReturnStatement;
import ms.unicorn.dsl.unicornDSL.impl.DslConvertionSchemeImpl;

import org.eclipse.emf.ecore.EObject;

public class ConversionScheme extends DslConvertionSchemeImpl implements
		IConversionScheme {
	
	private ExecutionContext context = new DefaultExecutionContext(null, null);
	private String language;
	private String scheme;
	
	@SuppressWarnings("unchecked")
	public <T> T getElementProperty(String name, T defvalue, ExecutionContext context) {
		// first look for property name in this model element
		for (EObject eprop: this.getProperties()) 
			if (eprop instanceof NamedProperty) {
				NamedProperty prop = (NamedProperty) eprop;
				if (prop.getName().equalsIgnoreCase(name)) {
					return (T)SphynxInterpreter.INSTANCE.resolveValue(prop.getValue(), context);
				}
			}
		// then look for this property in the parent element if any.
		if (this.getInheritance() instanceof ILanguageElement) 
			return ((ILanguageElement) this.getInheritance()).getElementProperty(name, defvalue, context);
		// still not found? return the defvalue.
		return defvalue;
	}
	
	public <T> T getElementProperty(String name, T defvalue) {
		return getElementProperty(name, defvalue, context);
	}
	
	public String getLanguage() {
		if (this.language == null) 
			this.language = getElementProperty("language", "");
		return language;
	}
	
	public String getScheme() {
		if (this.scheme == null) 
			this.scheme = getElementProperty("scheme", "");
		return scheme;
	}
	
	@Override
	public boolean isConverterFor(String language, String scheme) {
		return getLanguage().equals(language) && getScheme().equals(scheme);
	}
	
	@Override
	public <T> T convert(Element domainObject, IConversionCache cache) {
		Object obj =  cache.getConvertedObject(domainObject.getId());
		if (obj == null) {
			// prepare the context
			ExecutionContext context = this.context.createChildContext();
			context.allocateVariable(VariableStorage.READONLY, false, "converter", this);
			context.allocateVariable(VariableStorage.READONLY, false, "conversion_cache", cache);
			context.allocateVariable(VariableStorage.READONLY, false, "conversion_context", context);
			context.allocateVariable(VariableStorage.READONLY, false, "console", System.out);
			return convert(domainObject, cache, context);
		}
		return (T)obj;
	}
	
	public <T> T convert(Element domainObject, IConversionCache cache, ExecutionContext context) {
		Object obj =  cache.getConvertedObject(domainObject.getId());
		if (obj == null) {
			// run the corresponding rule
			ILanguageElement element = LanguageRegistry.INSTANCE.getLanguageElement(domainObject);
		
			// prepare the context
			ExecutionContext childContext = context.createChildContext();
			childContext.allocateVariable(VariableStorage.READONLY, false, "object", domainObject);
			obj = getElementProperty(element.getId(), VariableStorage.EMPTY, childContext);
			if (obj instanceof XFunctionDeclaration) {
				obj = XFunctionDeclartationInterpreter.interpret(SphynxInterpreter.INSTANCE, (XFunctionDeclaration)obj, childContext);
			}
			if (obj instanceof XReturnStatement) {
				XReturnStatement res = (XReturnStatement) obj;
				if (res.getValue() != null)
					obj = SphynxInterpreter.INSTANCE.resolveValue(res.getValue(), childContext);
				else obj = null;
			}
			if (obj == VariableStorage.EMPTY) {
				System.out.println(String.format("WARNING: Convertion rule is missing for element '%s'", element.getId()));
				return null;
				//throw new SphynxRuntimeException(childContext, this, String.format("Convertion rule is missing for element '%s'", element.getId()));
			}
			
			if (domainObject instanceof Diagram)
					context.allocateVariable(VariableStorage.READONLY, false, "converted_diagram", obj);
			cache.map(domainObject, obj);
			
			for (Element child: domainObject.getElements())
				convert(child, cache, context);
		}
		return (T)obj;
	}
}
